let activeEffect;
const effectStack: any[] = []; // 处理effect嵌套

const jobQueue: Set<any> = new Set(); // 相同副作用仅执行一次，本集合用于去除副作用
const p = Promise.resolve();
const ITERATE_KEY = Symbol();
const MAP_KEY_ITERATE_KEY = Symbol();

const TriggerType = {
    SET: 'SET',
    ADD: 'ADD',
    DELETE: 'DELETE',
    SELECT: 'SELECT',
}

const reactiveMap = new Map();

let isFlushing = false;

let shouldTrack = true;


const arrayInstrumentations = {};
// 重写数组查找方法
['includes', 'indexOf', 'lastIndexOf'].forEach(method => {
    const originMethod = Array.prototype[method];
    arrayInstrumentations[method] = function (...args) {
        let res = originMethod.call(this, ...args);
        if (res === false) {
            res = originMethod.call(this.raw, ...args);
        }
        return res;
    }
});

// 重写数组修改方法
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(method => {
    const originMethod = Array.prototype[method];
    arrayInstrumentations[method] = function (...args) {
        shouldTrack = false;
        let res = originMethod.call(this, ...args);
        shouldTrack = true;
        return res;
    }
})

const iterationMethod = function() {
    const target = this.raw;
    const itr = target[Symbol.iterator]();

    const wrap = val => (typeof val === 'object' && val !== null) ? reactive(val) : val;
    track(target, ITERATE_KEY)

    return {
        next() {
            const { value, done } = itr.next();
            return {
                value: value ? [wrap[value[0]], wrap(value[1])] : value,
                done
            }
        },
        [Symbol.iterator]() {
            return this;
        }
    };
}

const valuesIterationMethod = function() {
    const target = this.raw;
    const itr = target.values();

    const wrap = val => (typeof val === 'object' && val !== null) ? reactive(val) : val;
    track(target, ITERATE_KEY)

    return {
        next() {
            const { value, done } = itr.next();
            return {
                value: wrap(value),
                done
            }
        },
        [Symbol.iterator]() {
            return this;
        }
    };
}

const keysIterationMethod = function() {
    const target = this.raw;
    const itr = target.keys();

    const wrap = val => (typeof val === 'object' && val !== null) ? reactive(val) : val;
    track(target, MAP_KEY_ITERATE_KEY)

    return {
        next() {
            const { value, done } = itr.next();
            return {
                value: wrap(value),
                done
            }
        },
        [Symbol.iterator]() {
            return this;
        }
    };
}

// MAP、SET 重写方法
const mutableInstrumentations = {
    get(key) {
        const target = this.raw;
        const hadKey = target.has(key);
        trigger(target, key);
        if (hadKey) {
            const res = target.get(key);
            return typeof res === 'object'? reactive(res) : res;
        }
    },
    set(key, newValue) {
        const target = this.raw;
        const hadKey = target.has(key);
        const oldValue = target.get(key);
        const rawValue = newValue.raw ?? newValue;
        target.set(key, rawValue);
        if (!hadKey) {
            trigger(target, key, 'ADD');
        } else if (oldValue !== newValue && (!Number.isNaN(oldValue) || !Number.isNaN(newValue))) {
            trigger(target, key, 'SET')
        }
    },
    add(key) {
        const target = this.raw;
        const hadKey = target.has(key);
        const res = target.add(key);
        if (!hadKey) {
            trigger(target, key, 'ADD');
        }
        return res;
    },
    delete(key) {
        const target = this.raw;
        const hadKey = target.has(key);
        const res = target.delete(key);
        if (hadKey) {
            trigger(target, key, 'DELETE');
        }
        return res;
    },
    forEach(callback, thisArg) {
        const wrap = val => (typeof val === 'object' && val !== null) ? reactive(val) : val;
        const target = this.raw;
        track(target, ITERATE_KEY)
        target.forEach((v, k) => callback.call(thisArg, wrap(v), wrap(k), this));
    },
    [Symbol.iterator]: iterationMethod,
    entries: iterationMethod,
    values: valuesIterationMethod,
    keys: keysIterationMethod,
};

const bucket = new WeakMap();

const track = (target, key) => {

    if (!shouldTrack || !activeEffect) return ;

    let depsMap = bucket.get(target);
    if (!depsMap) bucket.set(target, (depsMap = new Map()));

    let deps = depsMap.get(key);
    if (!deps) deps.set(key, (deps = new Set()));

    deps.add(activeEffect);
    activeEffect.deps.push(deps);
}

const trigger = (target, key, type = 'SELECT', newValue = 0) => {
    const depsMap = bucket.get(target);
    if (!depsMap) return false;

    const effects = depsMap.get(key);
    const effectsToRun: Set<any> = new Set(); // 处理set无限循环

    effects && effects.forEach(effectFn => { // 处理相同副作用无限递归调用
        if (effectFn !== activeEffect) {
            effectsToRun.add(effectFn);
        }
    });

    // 如果操作对象是数组，且直接修改了length，
    // 将索引大于等于newValue的元素相关联副作用取出并添加到effectsToRun执行
    if (Array.isArray(target) && key === 'length') {
        depsMap.forEach((deps, key) => {
            if (key >= newValue) {
                effectsToRun.forEach(effectFn => {
                    if (effectFn !== activeEffect) {
                        effectsToRun.add(effectFn);
                    }
                })
            }
        })
    }

    if (['ADD', 'DELETE'].includes(type) || ( && type === 'SET')) {
        const iterateEffects = depsMap.get(ITERATE_KEY);
        iterateEffects && iterateEffects.forEach(effectFn => {
            if (effectFn !== activeEffect) {
                effectsToRun.add(effectFn);
            }
        })
    }

    if (target instanceof Map) {
        const map = new Map([
            ['ADD', MAP_KEY_ITERATE_KEY],
            ['DELETE', MAP_KEY_ITERATE_KEY],
            ['SET', ITERATE_KEY],
        ]);
        const iterateEffects = depsMap.get(map.get(type));
        iterateEffects && iterateEffects.forEach(effectFn => {
            if (effectFn !== activeEffect) {
                effectsToRun.add(effectFn);
            }
        })
    }

    effectsToRun && effectsToRun.forEach(effect => {
        if (effect.option.scheduler) {
            effect.option.scheduler(effect);
        } else {
            effect();
        }
    });

    return true;
}

const cleanup = (effectFn) => {
    for (let i = 0; i < effectFn.deps.length; i++) {
        const deps = effectFn.deps[i];
        deps.delete(effectFn);
    }
    effectFn.deps.length = 0;
}

// 刷新队列
const flushJob = () => {
    if (isFlushing) return;
    isFlushing = true;
    p.then(() => {
        jobQueue.forEach(job => job())
    }).finally(() => {
        isFlushing = false;
    })
}

const traverse = (value: any, seen = new Set()) => {
    if (typeof value !== 'object' || value === null || seen.has(value)) {
        return;
    }
    seen.add(value);

    for (const key in value) {
        traverse(value[key], seen);
    }

    return value;
}

const reactive = data => {
    // 优化： 防止重复创建响应式
    const existReactive = reactiveMap.get(data);
    if (existReactive) return existReactive;

    const proxy = createReactive(data);
    reactiveMap.set(data, proxy);

    return proxy;
};

// TODO： 代理数组、代理SET、MAP
const createReactive = (data, isShallow = false, isReadOnly = false) => {
    const obj = new Proxy(data, {
        get(target, key, receiver) {
            if (key === 'raw') return target; // 代理对象可以 通过raw属性获取原始数据
            
            const isSetOrMap = target instanceof Set || target instanceof Map; //针对MAP和SET
            if (isSetOrMap) {
                if(key === 'size') {
                    track(target, ITERATE_KEY);
                    return Reflect.get(target, 'size', target);
                    // return Reflect.get(target, 'size', target);
                }
                return mutableInstrumentations[key]
                // return target[key].bind(target); // 集合类数据操作时需要绑定到集合本身，而非代理对象上
            }


            // 重写数组方法
            if (Array.isArray(target) && arrayInstrumentations.hasOwnProperty(key)) {
                return Reflect.get(arrayInstrumentations, key, receiver);
            }

            // 优化：只读属性无需添加响应式
            // key的类型是symbol，不进行追踪, 针对对象for in 迭代器循环
            if (!isReadOnly && key !== 'symbol') {
                track(target, key);
            }

            const res = Reflect.get(target, key, receiver);

            if (!isShallow) return res;

            if (typeof res === 'object' && res !== null) {
                return isReadOnly ? readonly(res) : reactive(res);
            }
            return res;
        },
        set(target, key, newValue, receiver) {
            if (isReadOnly) {
                console.warn(`属性${String(key)}为只读属性，无法修改！${newValue}`)
            }

            const oldValue = target[key];
            const type = Array.isArray(target)
                ? Number(key) < target.length ? 'SET' : 'ADD'
                : Object.prototype.hasOwnProperty.call(target, key) ? 'SET' : 'ADD';
            const res = Reflect.set(target, key, newValue, receiver);
            if (target === receiver.raw) {
                if (oldValue !== newValue && (!Number.isNaN(newValue) || !Number.isNaN(oldValue))) {
                    trigger(target, key, type, newValue);
                }
            }
            return res;
        },
        deleteProperty(target, key) {
            const hadKey = Object.prototype.hasOwnProperty.call(target, key)
            const res = Reflect.deleteProperty(target, key);
            if (res && hadKey) {
                trigger(target, key, 'DELETE')
            }
            return res;
        },
        has(target, key) { // 查阅Ecma规范，for in 情况会调用has，因此也需要进行代理
            return Reflect.has(target, key);
        },
        ownKeys(target) {
            // 将副作用与 ITERATE_KEY关联
            track(target, Array.isArray(target) ? 'length' : ITERATE_KEY);
            return Reflect.ownKeys(target);

        }
    });

    return obj;
}

const readonly = (obj) => createReactive(obj, false, true);

const shallowReadonly = (obj) => createReactive(obj, true, true);


const ref = (val: any) => {
    const obj = {
        value: val,
    }
    Object.defineProperty(obj, '_v_isRef', {
        value: true,
        enumerable: false,
        writable: false,
    });
    return reactive(obj)
}

const toRef = (obj, key) => {
    const wrapper = {
        get value() {
            return obj[key];
        },
        set value(newValue) {
            obj[key] = newValue;
        }
    }
    Object.defineProperty(wrapper, '_v_isRef', {
        value: true,
        enumerable: false,
        writable: false,
    });
    return wrapper;
}

const toRefs = (obj) => {
    const res = {}

    for (const key in obj) {
        res[key] = toRef(obj, key);
    }
    return res;
}

/**
 * 自动脱ref
 * @param target 
 * @returns 
 */
const proxyRefs = (target) => {
    return new Proxy(target, {
        get(target, key, receiver) {
            const value = Reflect.get(target, key, receiver);
            return target._v_isRef ? value.value : value
        }
    })
}

const effect = (fn: any, option?: {
    scheduler?: any;
    lazy?: boolean;
}) => {
    const effectFn = () => {
        effectStack.push(effectFn);
        activeEffect = fn;

        cleanup(effectFn); // 处理分支切换
        const res = fn();

        effectStack.pop();
        activeEffect = effectStack[effectStack.length - 1];
        return res;
    };

    effectFn.deps = [];
    effectFn.option = option;
    if (!option?.lazy) return effectFn();
    return effectFn;
}

const computed = (getter) => {
    let value;
    let dirty = true;

    const effectFn = effect(getter, {
        lazy: true,
        scheduler() {
            if (dirty) return;
            trigger(obj, 'value');
            dirty = true;
            // 此处不需要指定副作用的原因是，
            // 在effectFn中，getter函数中访问的响应式数据，
            // 会触发track函数，在track函数中会将副作用函数添加到桶中，
            // 当数据变化时，会触发trigger函数，
            // 在trigger函数中会执行副作用函数，
            // 从而实现数据变化时，更新视图的效果。
        }
    });

    const obj = {
        get value() {
            if (dirty) {
                value = effectFn();
                dirty = false;
            }
            track(obj, 'value');
            return value;
        }
    }
    return obj;
}

const watch = (source, cb, option?: {
    immediate?: boolean;
    flush?: 'post' | 'sync';
    // flush?: 'pre' | 'post' | 'sync';
}) => {

    let getter;
    let cleanup;

    if (typeof source === 'function') {
        getter = source;
    } else {
        getter = () => traverse(source);
    }

    let oldValue, newValue;

    const onInvalidate = (fn) => {
        cleanup(effectFn);
    }

    const job = () => {
        newValue = effectFn();
        if (cleanup) cleanup()

        cb(newValue, oldValue, onInvalidate);
        oldValue = newValue;
    }

    const effectFn = effect(getter, {
        lazy: true,
        scheduler() {
            if (option?.flush === 'post') {
                const p = Promise.resolve();
                p.then(job);
            } else {
                job();
            }
        }
    });
    if (option?.immediate) {
        job();
    } else {
        oldValue = effectFn();
    }
}

const VueReactivity = {
    reactive,
    ref,
    effect,
    computed,
    watch,
    readonly,
    shallowReadonly,
    flushJob,
    toRef,
    toRefs, // 解决解构赋值时，响应丢失的问题
    proxyRefs,
}

export default VueReactivity;